// es6 --   computed
// 解决小程序  计算属性问题

/* 
 警告
 1.不支持   计算属性相互引用
 2.考虑小程序性能原因  computed里的属性请在初始化时定义好   暂不开放动态添加computed属性
 3.参考vue  computed里的属性 this 指向 data 并不是 page对象
*/
/* 
  
  使用示例
  1.首先引入函数
  const { $computed } = require("path");
  2.在页面或者组件生命周期上  onLoad 或者 attached 中初始化
  3.在初始化之前要把 computed设置好  不然会忽略掉  并发出一个警告提示
  page({
    data:{
      children: 23
    }
    ,computed:{
      parent(){
        return this.children + 25;
      }
    }
    ,onLoad(){
      $computed.call(this);
    }
  })
*/
/* 
  2021/4/24 
  新增  组件  计算功能  在 mixins里 已经全局开启  
  主意：
    ### 由于小程序组件构造器的方法
   1. 在定义 properties ,data 里 key不应出现  __computedData__  这个字段
   2. 必须在  app.js 开启  全局混入
  示例:
  Component({
    data:{
      age: 12
    }
    ,computed:{
      ageAdd(){
        return this.age + 1;
      }
    }
  })
*/
const comUtils = require("../../utils/common");
const computedConfig = require("./config");

module.exports = function $computed(isPage = true) {
  if (!computedConfig.openComputedAuto) {
    return;
  }
  const page = this;
  // 如果已经设置计算属性 直接阻断
  if (page[computedConfig.computedIsConfigableKey]) {
    return;
  }
  let computed;
  const { data } = page;
  if (isPage) {
    computed = page[computedConfig.computedKey_page];
  } else {
    computed = page.data[computedConfig.computedKey_component];
  }
  //  console.log( computed );
  // 设置一个变量保存computed是否设置成功 优化初始化速度
  let setDataAuto = false;
  // 这些判断只在第一次初始化的时候进行判断
  // 获取computed对象类型 如果不是对象Object类型 下面不进行冲突判断
  if (computed) {
    let computedClass = comUtils.toClass(computed);
    if (computedClass == "Object") {
      // 对 computed 属性进行类型检查  如果不是函数就抛出一个错误
      // 对 computed 与 data 属性进行对比,如果有相同的就抛出一个错误
      let computedKeys = Object.keys(computed); //利用闭包优化后续代理查询速度
      for (let i = 0, len = computedKeys.length; i < len; i++) {
        let key = computedKeys[i];
        if (typeof computed[key] != "function") {
          throw new TypeError(
            `the computed [${key}] property is not a function`
          );
        }
        if (comUtils.hasOwn(data, key)) {
          throw new Error(`the computed [${key}] property is defined in data`);
        }
      }
      // 把setDataAuto设置为true
      setDataAuto = true;
      // 保存page的原生setData函数 利用闭包优化代理setData速度
      const _setData = page.setData;
      // 设置 page setData代理函数
      Object.defineProperty(page, "setData", {
        // 不可再次配置
        configurable: false,
        enumerable: true,
        writable: false,
        value: function (_data) {
          //判断data存在
          if (_data) {
            // 判断data类型
            if (comUtils.toClass(_data) == "Object") {
              let dataKeys = Object.keys(_data);
              // 判断传入参数是不是一个空对象(没有属性) {}  并发出一个警告
              // 声明一个变量合并改变的属性和值(包括 传入参数 和 computed属性 )
              let assignData = {};
              // 合并传参到data对象
              for (let i = 0, len = dataKeys.length; i < len; i++) {
                //获取 传入属性
                let key = dataKeys[i];
                data[key] = assignData[key] = _data[key];
                //判断 computed 属性是否 与 传入属性冲突
                if (!comUtils.hasOwn(computed, key)) {
                  data[key] = _data[key];
                } else {
                  throw new Error(
                    `this attribute [${key}] has been defined in computed`
                  );
                  break;
                }
              }
              //  属性合并完成后  进行一次 computed 重计算合并到 data 对象里面
              // 由于小程序setData限制 这里就不做 data 与 computed 属性冲突判断
              computedKeys.forEach((key) => {
                data[key] = assignData[key] = computed[key].call(data);
              });
              // 合并完成 调用原型setData渲染视图
              _setData.call(page, assignData);
            } else {
              throw new TypeError("the data is must be Object");
            }
          } else {
            throw new ReferenceError("the data in not defined");
          }
        },
      });
      // 如果外部属性写入导致冲突
      if (!comUtils.hasOwn(page, computedConfig.computedIsConfigableKey)) {
        // 设置 page 一个变量表示 setData 已经被配置过  不可配置 不可写入
        Object.defineProperty(page, computedConfig.computedIsConfigableKey, {
          value: true,
          configurable: false,
          writable: false,
        });
      } else {
        throw Error(
          `open Computed , Page or Component Instance key ${computedConfig.computedIsConfigableKey}  are not configurable`
        );
      }
    } else {
      console.warn(
        `ignore: the computed ClassType is [Object ${computedClass}]`
      );
    }
  }
  /* else {
      console.warn("ignore: the computed is not defined");
    } */
  // 如果computed设置成功  则进行一次初始化  实例化computed属性到 data
  if (setDataAuto) {
    page.setData({});
  }
};
